package com.shioirikukuri.service.impl;

import com.shioirikukuri.common.exception.BusinessException;
import com.shioirikukuri.common.web.CommonCodeMsg;
import com.shioirikukuri.common.web.Result;
import com.shioirikukuri.domain.*;
import com.shioirikukuri.mapper.OrderInfoMapper;
import com.shioirikukuri.mapper.PayLogMapper;
import com.shioirikukuri.mapper.RefundLogMapper;
import com.shioirikukuri.mq.MQConstant;
import com.shioirikukuri.mq.OrderMessage;
import com.shioirikukuri.redis.SeckillRedisKey;
import com.shioirikukuri.service.IOrderInfoService;
import com.shioirikukuri.service.ISeckillProductService;
import com.shioirikukuri.util.IdGenerateUtil;
import com.shioirikukuri.web.config.UserPhone;
import com.shioirikukuri.web.feign.IntegralFeignApi;
import com.shioirikukuri.web.feign.PayFeignApi;
import com.shioirikukuri.web.msg.SeckillCodeMsg;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.SneakyThrows;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by  shioirikukuri
 */
@Service
public class OrderInfoSeviceImpl implements IOrderInfoService {

    @Autowired
    @Lazy
    private IOrderInfoService orderInfoService;
    @Autowired
    private ISeckillProductService seckillProductService;
    @Autowired
    private OrderInfoMapper orderInfoMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private PayLogMapper payLogMapper;
    @Autowired
    private RefundLogMapper refundLogMapper;

    @Autowired
    private PayFeignApi payFeignApi;

    public static ConcurrentHashMap<Long,Boolean> stockCountOverFlagMap = new ConcurrentHashMap<>();


    @Override
    //@Transactional 不再使用事务,而是用Redis判断,然后发给MQ异步去请求
    public void doSeckill(Long seckillId, @UserPhone String phone, String token){
        //判断本地标识
        Boolean stockCountOverFlag = stockCountOverFlagMap.get(seckillId);
        if(stockCountOverFlag!=null&&stockCountOverFlag){
            throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
        }

        SeckillProduct seckillProduct = seckillProductService.find(seckillId);
        //1.请求时间控制判断
        /*
        boolean legalTime = DateUtil.isLegalTime(seckillProduct.getStartDate(),seckillProduct.getTime());
        if(!legalTime){
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }
        */

        //2.判断是否重复秒杀,一人一份
        String orderSetKey = SeckillRedisKey.SECKILL_ORDER_SET.getRealKey(seckillId.toString());
        if(redisTemplate.opsForSet().isMember(orderSetKey,phone)){
            //判断Ridis缓存中订单是否已经存在
            throw new BusinessException(SeckillCodeMsg.REPEAT_SECKILL);
        }


        //3.减少Redis库存
        String stockCountKey = SeckillRedisKey.SECKILL_PRODUCT_STOCK.getRealKey((seckillId.toString()));
        long remainCount = redisTemplate.opsForValue().decrement(stockCountKey);
        if(remainCount<0){
            //避免Redis库存变为负值，保持在0
            //存在Redis增减操作过多的问题
            redisTemplate.opsForValue().increment(stockCountKey);
            stockCountOverFlagMap.put(seckillId,true);
            //如果库存为0，则抛出异常,减少流量
            throw new BusinessException(SeckillCodeMsg.SECKILL_STOCK_OVER);
        }

        OrderMessage msg = new OrderMessage();
        msg.setSeckillId(seckillId);
        msg.setUserPhone(phone);
        msg.setToken(token);

        //发送MQ消息
        SendResult result = rocketMQTemplate.syncSend(MQConstant.ORDER_CREATE_TOPIC, msg);
        System.out.println("发送结果:"+result.getSendStatus());
        if(!SendStatus.SEND_OK.equals(result.getSendStatus())){
            redisTemplate.opsForValue().increment(stockCountKey);
            throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
        }
    }

    /**
     * 创建订单
     * 如果创建订单失败，则回滚Redis库存
     * @param seckillId
     * @param userPhone
     * @return
     */
    @Override
    @Transactional
    public String createSeckillOrder(Long seckillId, String userPhone) {
        //4.扣除商品库存
        int effCount = seckillProductService.decrStockCount(seckillId);
        if(effCount==0){
            //如果没有存货,则不创建订单直接抛出异常
            throw new BusinessException(SeckillCodeMsg.SECKILL_ERROR);
        }
        String orderNo = null;
        try{
            //5.创建订单
            orderNo = createOrder(seckillId,userPhone);
        }catch (Exception e){
            //此处应该有回滚逻辑
            String stockCountKey = SeckillRedisKey.SECKILL_PRODUCT_STOCK.getRealKey((seckillId.toString()));

            //创建订单失败，则回滚库存
            //同步Redis中的库存
            redisTemplate.opsForValue().increment(stockCountKey);

            //不再依赖直接修改本地标识，而是通过MQ广播控制所有集成服务同时修改标识
            rocketMQTemplate.syncSend(MQConstant.STOCK_OVER_FLAG_TOPIC,seckillId);

            //抛出异常回滚
            throw e;
        }
        return orderNo;
    }

    private String createOrder(Long seckillId, String phone) {
        SeckillProduct seckillProduct = seckillProductService.find(seckillId);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setSeckillId(seckillId);
        orderInfo.setSeckillDate(seckillProduct.getStartDate());
        orderInfo.setSeckillPrice(seckillProduct.getSeckillPrice());
        orderInfo.setSeckillTime(seckillProduct.getTime());
        orderInfo.setProductPrice(seckillProduct.getProductPrice());
        orderInfo.setProductImg(seckillProduct.getProductImg());
        orderInfo.setPhone(phone);
        orderInfo.setIntegral(seckillProduct.getIntegral());
        orderInfo.setCreateDate(new Date());
        orderInfo.setProductName(seckillProduct.getProductName());
        orderInfo.setOrderNo(IdGenerateUtil.get().nextId()+"");
        orderInfoMapper.insert(orderInfo);


        return orderInfo.getOrderNo();

        /*SeckillProductVo vo = seckillProductService.find(seckillId);
        OrderInfo orderInfo = new OrderInfo();
        BeanUtils.copyProperties(vo,orderInfo);

        orderInfo.setOrderNo(IdGenerateUtil.get().nextId()+"");
        orderInfo.setUserId(Long.parseLong(phone));
        orderInfo.setCreateDate(new Date());
        orderInfo.setSeckillDate(vo.getStartDate());
        orderInfo.setSeckillTime(vo.getTime());
        orderInfo.setSeckillId(vo.getId());
        orderInfoMapper.insert(orderInfo);*/
    }

    @Override
    public OrderInfo find(String orderNo) {
        return orderInfoMapper.find(orderNo);
    }

    @Override
    @Transactional
    public void orderPayTimeoutCheck(String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.find(orderNo);

        if(OrderInfo.STATUS_UNPAID.equals(orderInfo.getStatus())){
            //订单未支付

            /* 超时检查暂时关闭 TODO 生产环境请开启
            //修改订单状态为超时
            int effectCount = orderInfoMapper.changeOrderStatus(
                    orderNo,
                    OrderInfo.STATUS_TIMEOUT,
                    OrderInfo.STATUS_UNPAID
            );


            //如果其他进程已将支付过程完成
            if(effectCount==0){
                //影响行数为0说明无需执行后续操作
                return;
            }
            //增加数据库库存
            seckillProductService.incrStockCount(orderInfo.getSeckillId());

             */
        }

    }
    @Value("${pay.notifyUrl}")
    private String notifyUrl;
    @Value("${pay.returnUrl}")
    private String returnUrl;


    @Override
    public String pay(String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.find(orderNo);

        if(orderInfo==null){
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }
        if(!OrderInfo.STATUS_UNPAID.equals(orderInfo.getStatus())){
            return "";
        }
        PayVo vo = new PayVo();
        vo.setOutTradeNo(orderNo);
        vo.setTotalAmount(orderInfo.getSeckillPrice()+"");
        vo.setSubject(orderInfo.getProductName());
        vo.setBody(orderInfo.getProductName());
        vo.setReturnUrl(returnUrl);
        vo.setNotifyUrl(notifyUrl);
        Result<String> result = payFeignApi.pay(vo);
        /**
         * 情况一:
         * result == null
         * 支付服务不可用,降级服务
         * 情况二:
         * result != null,code!=200
         * 支付服务可用,但是支付失败,内部出错
         * 情况三:
         * result != null,code==200
         * 服务正常
         *
         */
        if(result==null && result.hasError()){
            throw new BusinessException(SeckillCodeMsg.PAY_SERVER_ERROR);
        }
        String html = result.getData();
        return html;
    }

    @Override
    public void orderNotify(Map<String, String> params) {
        //1.进行验签操作
        Result<Boolean> result = payFeignApi.rsaCheckV1(params);
        if(result==null || result.hasError() || !result.getData())
        {
            throw new BusinessException(SeckillCodeMsg.PAY_SERVER_ERROR);
        }
        //2.验签成功,执行业务逻辑
        String orderNo = params.get("out_trade_no");

        int effectCount = orderInfoMapper.changeOrderToPayStatus(
                orderNo,
                OrderInfo.STATUS_ACCOUNT_PAID,
                OrderInfo.STATUS_UNPAID,
                OrderInfo.PAYTYPE_ONLINE
        );

        if(effectCount==0){
            //订单回调时,其他线程已经修改了,走退款逻辑
            //插入到数据表中,客服审核通过之后,才对订单进行退款(本系统无客服服务)
            return;
        }

    }

    @Override
    public String orderReturn(Map<String, String> params) {
        Result<Boolean> result = payFeignApi.rsaCheckV1(params);
        if(result==null || result.hasError() || !result.getData())
        {
            throw new BusinessException(SeckillCodeMsg.PAY_SERVER_ERROR);
        }

        String orderNo = params.get("out_trade_no");
        return orderNo;
    }

    @Override
    public void refund(String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.find(orderNo);
        if(orderInfo==null){
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }
        if(!OrderInfo.STATUS_ACCOUNT_PAID.equals(orderInfo.getStatus())){
            return;
        }
        if(orderInfo.PAYTYPE_ONLINE.equals(orderInfo.getPayType())){
            //在线支付退款
            this.refundOnline(orderInfo);
        }else{
            //积分支付退款
            orderInfoService.refundByIntegral(orderInfo);
        }
    }

    /**
     * 积分支付退款
     * @param orderInfo
     */
    @SneakyThrows
    @GlobalTransactional
    public void refundByIntegral(OrderInfo orderInfo) {
        //插入退款记录(订单号作为唯一主键)
        RefundLog log = new RefundLog();
        log.setOrderNo(orderInfo.getOrderNo());
        log.setRefundAmount(orderInfo.getIntegral());
        log.setRefundReason("此处未添加响应模块");
        log.setRefundTime(new Date());

        try{
            refundLogMapper.insert(log);
        }catch (Exception e){
            return;
        }

        //远程调用积分服务,给指定用户增加积分
        OperateIntegralVo vo = new OperateIntegralVo();

        vo.setPhone(orderInfo.getPhone());
        vo.setValue(orderInfo.getIntegral());

        Result<String> result = integralFeignApi.refund(vo);
        if(result==null||result.hasError()){
            throw new BusinessException(SeckillCodeMsg.INTEGRAL_SERVER_ERROR);
        }
        //人造异常测试，测试全局回滚
        //int i = 1/0;


        //修改订单状态,改为已退款
        int effectCount = orderInfoMapper.changeOrderStatus(
                orderInfo.getOrderNo(),
                OrderInfo.STATUS_REFUND,
                OrderInfo.STATUS_ACCOUNT_PAID
        );

        if(effectCount==0){
            //人工处理(本系统无客服服务)

        }

    }

    /**
     * 在线支付退款
     * @param orderInfo
     */
    private void refundOnline(OrderInfo orderInfo) {
        //调用支付服务进行退款
        RefundVo vo = new RefundVo();
        vo.setOutTradeNo(orderInfo.getOrderNo());
        //退款金额(需要和订单金额一致)
        vo.setRefundAmount(orderInfo.getSeckillPrice().toString());
        vo.setRefundReason("未加入申诉理由功能");

        Result<Boolean> result = payFeignApi.refund(vo);
        if (result==null || result.hasError() || !result.getData()){
            throw new BusinessException(SeckillCodeMsg.PAY_SERVER_ERROR);
        }
        //修改订单状态，改成已退款状态
        int effectCount = orderInfoMapper.changeOrderStatus(
                orderInfo.getOrderNo(),
                OrderInfo.STATUS_REFUND,
                OrderInfo.STATUS_ACCOUNT_PAID
        );

        if(effectCount==0){
            //如果出现异常,通知客服处理(本系统无该模块)
            return;
        }
    }
    @Autowired
    private IntegralFeignApi integralFeignApi;
    @Override
    @GlobalTransactional
    public void payByIntegral(String orderNo) {
        OrderInfo orderInfo =orderInfoMapper.find(orderNo);
        if (orderInfo==null){
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }
        if (!OrderInfo.STATUS_UNPAID.equals(orderInfo.getStatus())){
            return;
        }
        //1.插入支付日志(保证幂等性)
        PayLog log =new PayLog();
        log.setOrderNo(orderNo);
        log.setPayTime(new Date());//支付时间
        log.setTotalAmount(orderInfo.getIntegral());

        try{
            payLogMapper.insert(log);
        } catch (Exception e){
            return;
        }
        //2.调用积分服务,扣减积分
        OperateIntegralVo vo = new OperateIntegralVo();
        vo.setPhone(orderInfo.getPhone());
        vo.setValue(orderInfo.getIntegral());
        Result<Boolean> result = integralFeignApi.pay(vo);


        if (result==null || result.hasError()){
            throw new BusinessException(SeckillCodeMsg.INTEGRAL_SERVER_ERROR);
        }
        if(!result.getData()){
            throw new BusinessException(SeckillCodeMsg.INTEGRAL_NOT_ENOUGH);
        }

        //模拟异常
        //int i = 1/0;

        //3.修改订单状态
        int effectCount = orderInfoMapper.changeOrderToPayStatus(
                orderNo,
                OrderInfo.STATUS_ACCOUNT_PAID,
                OrderInfo.STATUS_UNPAID,
                OrderInfo.PAYTYPE_INTEGRAL
        );
        if(effectCount==0){
            throw new BusinessException(SeckillCodeMsg.ORDER_STATUS_CHANGE);
        }
        return;
    }
}
